package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestSetCarSetCdrIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{
			"(define p (cons 1 2))",
			"",
		},
		{
			"p",
			"(1 . 2)",
		},
		{
			"(set-car! p 10)",
			"",
		},
		{
			"p",
			"(10 . 2)",
		},
		{
			"(set-cdr! p 20)",
			"",
		},
		{
			"p",
			"(10 . 20)",
		},
		{
			"(define lst (list 1 2 3))",
			"",
		},
		{
			"lst",
			"(1 2 3)",
		},
		{
			"(set-car! lst 'a)",
			"",
		},
		{
			"lst",
			"(a 2 3)",
		},
		{
			"(set-cdr! lst '(b c))",
			"",
		},
		{
			"lst",
			"(a b c)",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		result, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		// For procedures that return unspecified values (like set-car! and set-cdr!),
		// we expect NULL_OBJECT which prints as an empty string
		actual := result.Inspect()
		if actual != tt.expected {
			t.Errorf("Test %d: For input %q, expected %q, got %q", i, tt.input, tt.expected, actual)
		}
	}
}

func TestSetCarSetCdrErrorCases(t *testing.T) {
	tests := []struct {
		input    string
		expected string // Error message prefix
	}{
		{
			"(set-car! 1 2)",
			"set-car! first argument must be a pair",
		},
		{
			"(set-cdr! 1 2)",
			"set-cdr! first argument must be a pair",
		},
		{
			"(set-car! (cons 1 2))",
			"set-car! requires exactly 2 arguments",
		},
		{
			"(set-cdr! (cons 1 2))",
			"set-cdr! requires exactly 2 arguments",
		},
		{
			"(set-car! (cons 1 2) 3 4)",
			"set-car! requires exactly 2 arguments",
		},
		{
			"(set-cdr! (cons 1 2) 3 4)",
			"set-cdr! requires exactly 2 arguments",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		_, err = EvalExpression(expr, env)
		if err == nil {
			t.Errorf("Test %d: Expected error for input %q, but got none", i, tt.input)
		} else if err.Error() != tt.expected {
			t.Errorf("Test %d: For input %q, expected error %q, got %q", i, tt.input, tt.expected, err.Error())
		}
	}
}

func TestCarCdrAccessorsIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{
			"(define lst '((1 2) 3 4))",
			"",
		},
		{
			"(car lst)",
			"(1 2)",
		},
		{
			"(cdr lst)",
			"(3 4)",
		},
		{
			"(caar lst)",
			"1",
		},
		{
			"(cadr lst)",
			"3",
		},
		{
			"(cdar lst)",
			"(2)",
		},
		{
			"(cddr lst)",
			"(4)",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		result, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		actual := result.Inspect()
		if actual != tt.expected {
			t.Errorf("Test %d: For input %q, expected %q, got %q", i, tt.input, tt.expected, actual)
		}
	}
}

func TestCarCdrAccessorsErrorCases(t *testing.T) {
	tests := []struct {
		input    string
		expected string // Error message prefix
	}{
		{
			"(caar 1)",
			"caar argument must be a pair",
		},
		{
			"(cadr 1)",
			"cadr argument must be a pair",
		},
		{
			"(cdar 1)",
			"cdar argument must be a pair",
		},
		{
			"(cddr 1)",
			"cddr argument must be a pair",
		},
		{
			"(caar)",
			"caar requires exactly 1 argument",
		},
		{
			"(cadr)",
			"cadr requires exactly 1 argument",
		},
		{
			"(cdar)",
			"cdar requires exactly 1 argument",
		},
		{
			"(cddr)",
			"cddr requires exactly 1 argument",
		},
		{
			"(caar (cons 1 2) (cons 3 4))",
			"caar requires exactly 1 argument",
		},
		{
			"(cadr (cons 1 2) (cons 3 4))",
			"cadr requires exactly 1 argument",
		},
		{
			"(cdar (cons 1 2) (cons 3 4))",
			"cdar requires exactly 1 argument",
		},
		{
			"(cddr (cons 1 2) (cons 3 4))",
			"cddr requires exactly 1 argument",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		_, err = EvalExpression(expr, env)
		if err == nil {
			t.Errorf("Test %d: Expected error for input %q, but got none", i, tt.input)
		} else if err.Error() != tt.expected {
			t.Errorf("Test %d: For input %q, expected error %q, got %q", i, tt.input, tt.expected, err.Error())
		}
	}
}

func TestNullAndListPredicatesIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{
			"(null? '())",
			"#t",
		},
		{
			"(null? (cdr '(1)))",
			"#t",
		},
		{
			"(null? '(1))",
			"#f",
		},
		{
			"(null? 1)",
			"#f",
		},
		{
			"(list? '())",
			"#t",
		},
		{
			"(list? '(1 2 3))",
			"#t",
		},
		{
			"(list? '(1 2 . 3))",
			"#f",
		},
		{
			"(list? 1)",
			"#f",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		result, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		actual := result.Inspect()
		if actual != tt.expected {
			t.Errorf("Test %d: For input %q, expected %q, got %q", i, tt.input, tt.expected, actual)
		}
	}
}

func TestNullAndListPredicatesErrorCases(t *testing.T) {
	tests := []struct {
		input    string
		expected string // Error message prefix
	}{
		{
			"(null?)",
			"null? requires exactly 1 argument",
		},
		{
			"(list?)",
			"list? requires exactly 1 argument",
		},
		{
			"(null? 1 2)",
			"null? requires exactly 1 argument",
		},
		{
			"(list? 1 2)",
			"list? requires exactly 1 argument",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		_, err = EvalExpression(expr, env)
		if err == nil {
			t.Errorf("Test %d: Expected error for input %q, but got none", i, tt.input)
		} else if err.Error() != tt.expected {
			t.Errorf("Test %d: For input %q, expected error %q, got %q", i, tt.input, tt.expected, err.Error())
		}
	}
}

func TestListManipulationIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{
			"(length '())",
			"0",
		},
		{
			"(length '(1 2 3))",
			"3",
		},
		{
			"(append)",
			"()",
		},
		{
			"(append '(1 2))",
			"(1 2)",
		},
		{
			"(append '(1 2) '(3 4))",
			"(1 2 3 4)",
		},
		{
			"(append '(1 2) '(3 4) '(5 6))",
			"(1 2 3 4 5 6)",
		},
		{
			"(reverse '())",
			"()",
		},
		{
			"(reverse '(1 2 3))",
			"(3 2 1)",
		},
		{
			"(list-tail '(1 2 3 4) 0)",
			"(1 2 3 4)",
		},
		{
			"(list-tail '(1 2 3 4) 2)",
			"(3 4)",
		},
		{
			"(list-tail '(1 2 3 4) 4)",
			"()",
		},
		{
			"(list-ref '(1 2 3 4) 0)",
			"1",
		},
		{
			"(list-ref '(1 2 3 4) 2)",
			"3",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		result, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		actual := result.Inspect()
		if actual != tt.expected {
			t.Errorf("Test %d: For input %q, expected %q, got %q", i, tt.input, tt.expected, actual)
		}
	}
}

func TestListManipulationProceduresErrorCases(t *testing.T) {
	tests := []struct {
		input    string
		expected string // Error message prefix
	}{
		{
			"(length)",
			"length requires exactly 1 argument",
		},
		{
			"(length 1 2)",
			"length requires exactly 1 argument",
		},
		{
			"(length 1)",
			"length argument must be a list",
		},
		{
			"(append 1)",
			"append arguments must be lists",
		},
		{
			"(reverse)",
			"reverse requires exactly 1 argument",
		},
		{
			"(reverse 1 2)",
			"reverse requires exactly 1 argument",
		},
		{
			"(reverse 1)",
			"reverse argument must be a list",
		},
		{
			"(list-tail)",
			"list-tail requires exactly 2 arguments",
		},
		{
			"(list-tail 1)",
			"list-tail requires exactly 2 arguments",
		},
		{
			"(list-tail 1 2 3)",
			"list-tail requires exactly 2 arguments",
		},
		{
			"(list-tail 1 2)",
			"list-tail first argument must be a list",
		},
		{
			"(list-tail '(1 2 3) 'a)",
			"list-tail second argument must be a number",
		},
		{
			"(list-tail '(1 2 3) -1)",
			"list-tail second argument must be non-negative",
		},
		{
			"(list-tail '(1 2 3) 5)",
			"list-tail index out of bounds",
		},
		{
			"(list-ref)",
			"list-ref requires exactly 2 arguments",
		},
		{
			"(list-ref 1)",
			"list-ref requires exactly 2 arguments",
		},
		{
			"(list-ref 1 2 3)",
			"list-ref requires exactly 2 arguments",
		},
		{
			"(list-ref 1 2)",
			"list-ref first argument must be a list",
		},
		{
			"(list-ref '(1 2 3) 'a)",
			"list-ref second argument must be a number",
		},
		{
			"(list-ref '(1 2 3) -1)",
			"list-ref second argument must be non-negative",
		},
		{
			"(list-ref '() 0)",
			"list-ref index out of bounds",
		},
		{
			"(list-ref '(1 2 3) 5)",
			"list-ref index out of bounds",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		_, err = EvalExpression(expr, env)
		if err == nil {
			t.Errorf("Test %d: Expected error for input %q, but got none", i, tt.input)
		} else if err.Error() != tt.expected {
			t.Errorf("Test %d: For input %q, expected error %q, got %q", i, tt.input, tt.expected, err.Error())
		}
	}
}

func TestMembershipProceduresIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		// memq tests with symbols (more reliable for eq? testing)
		{
			"(memq 'a '(a b c))",
			"(a b c)",
		},
		{
			"(memq 'b '(a b c))",
			"(b c)",
		},
		{
			"(memq 'd '(a b c))",
			"#f",
		},
		// memq tests with numbers (may return #f because numbers are not eq? unless same object)
		{
			"(memq 1 '(1 2 3))",
			"#f", // This is correct - numbers are not eq? unless they are the same object
		},

		// memv tests with numbers
		{
			"(memv 1 '(1 2 3))",
			"(1 2 3)",
		},
		{
			"(memv 2 '(1 2 3))",
			"(2 3)",
		},
		{
			"(memv 4 '(1 2 3))",
			"#f",
		},

		// member tests with numbers
		{
			"(member 1 '(1 2 3))",
			"(1 2 3)",
		},
		{
			"(member 2 '(1 2 3))",
			"(2 3)",
		},
		{
			"(member 4 '(1 2 3))",
			"#f",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		result, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		actual := result.Inspect()
		if actual != tt.expected {
			t.Errorf("Test %d: For input %q, expected %q, got %q", i, tt.input, tt.expected, actual)
		}
	}
}

func TestMembershipProceduresErrorCases(t *testing.T) {
	tests := []struct {
		input    string
		expected string // Error message prefix
	}{
		// memq error cases
		{
			"(memq)",
			"memq requires exactly 2 arguments",
		},
		{
			"(memq 1)",
			"memq requires exactly 2 arguments",
		},
		{
			"(memq 1 2 3)",
			"memq requires exactly 2 arguments",
		},
		{
			"(memq 1 2)",
			"memq second argument must be a list",
		},

		// memv error cases
		{
			"(memv)",
			"memv requires exactly 2 arguments",
		},
		{
			"(memv 1)",
			"memv requires exactly 2 arguments",
		},
		{
			"(memv 1 2 3)",
			"memv requires exactly 2 arguments",
		},
		{
			"(memv 1 2)",
			"memv second argument must be a list",
		},

		// member error cases
		{
			"(member)",
			"member requires exactly 2 arguments",
		},
		{
			"(member 1)",
			"member requires exactly 2 arguments",
		},
		{
			"(member 1 2 3)",
			"member requires exactly 2 arguments",
		},
		{
			"(member 1 2)",
			"member second argument must be a list",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		_, err = EvalExpression(expr, env)
		if err == nil {
			t.Errorf("Test %d: Expected error for input %q, but got none", i, tt.input)
		} else if err.Error() != tt.expected {
			t.Errorf("Test %d: For input %q, expected error %q, got %q", i, tt.input, tt.expected, err.Error())
		}
	}
}

func TestAssociationListProceduresIntegration(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		// assq tests with symbols
		{
			"(assq 'a '((a . 1) (b . 2) (c . 3)))",
			"(a . 1)",
		},
		{
			"(assq 'b '((a . 1) (b . 2) (c . 3)))",
			"(b . 2)",
		},
		{
			"(assq 'd '((a . 1) (b . 2) (c . 3)))",
			"#f",
		},
		// assq tests with numbers (may return #f because numbers are not eq? unless same object)
		{
			"(assq 1 '((1 . a) (2 . b) (3 . c)))",
			"#f", // This is correct - numbers are not eq? unless they are the same object
		},

		// assv tests with numbers
		{
			"(assv 1 '((1 . a) (2 . b) (3 . c)))",
			"(1 . a)",
		},
		{
			"(assv 2 '((1 . a) (2 . b) (3 . c)))",
			"(2 . b)",
		},
		{
			"(assv 4 '((1 . a) (2 . b) (3 . c)))",
			"#f",
		},

		// assoc tests with numbers
		{
			"(assoc 1 '((1 . a) (2 . b) (3 . c)))",
			"(1 . a)",
		},
		{
			"(assoc 2 '((1 . a) (2 . b) (3 . c)))",
			"(2 . b)",
		},
		{
			"(assoc 4 '((1 . a) (2 . b) (3 . c)))",
			"#f",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		result, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		actual := result.Inspect()
		if actual != tt.expected {
			t.Errorf("Test %d: For input %q, expected %q, got %q", i, tt.input, tt.expected, actual)
		}
	}
}

func TestAssociationListProceduresErrorCases(t *testing.T) {
	tests := []struct {
		input    string
		expected string // Error message prefix
	}{
		// assq error cases
		{
			"(assq)",
			"assq requires exactly 2 arguments",
		},
		{
			"(assq 1)",
			"assq requires exactly 2 arguments",
		},
		{
			"(assq 1 2 3)",
			"assq requires exactly 2 arguments",
		},
		{
			"(assq 1 2)",
			"assq second argument must be a list",
		},
		{
			"(assq 1 '(1 2 3))",
			"assq: association list elements must be pairs",
		},

		// assv error cases
		{
			"(assv)",
			"assv requires exactly 2 arguments",
		},
		{
			"(assv 1)",
			"assv requires exactly 2 arguments",
		},
		{
			"(assv 1 2 3)",
			"assv requires exactly 2 arguments",
		},
		{
			"(assv 1 2)",
			"assv second argument must be a list",
		},
		{
			"(assv 1 '(1 2 3))",
			"assv: association list elements must be pairs",
		},

		// assoc error cases
		{
			"(assoc)",
			"assoc requires exactly 2 arguments",
		},
		{
			"(assoc 1)",
			"assoc requires exactly 2 arguments",
		},
		{
			"(assoc 1 2 3)",
			"assoc requires exactly 2 arguments",
		},
		{
			"(assoc 1 2)",
			"assoc second argument must be a list",
		},
		{
			"(assoc 1 '(1 2 3))",
			"assoc: association list elements must be pairs",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for i, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("ParseExpression() error = %v", err)
		}

		_, err = EvalExpression(expr, env)
		if err == nil {
			t.Errorf("Test %d: Expected error for input %q, but got none", i, tt.input)
		} else if err.Error() != tt.expected {
			t.Errorf("Test %d: For input %q, expected error %q, got %q", i, tt.input, tt.expected, err.Error())
		}
	}
}
